//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>

#include <buola/datetime/cdatetime.h>
#include <buola/os/cenv.h>

#include <unistd.h>
#include <time.h>
#include <cwchar>

namespace buola {

static mutex sTimeMutex;

static time_t tm_to_time_t(tm &pTM,const CTimeZone &pTZ)
{
    if(pTZ.IsLocal())
    {
        lock_guard<mutex> lLock(sTimeMutex);
        return mktime(&pTM);
    }
    else if(pTZ.IsUTC())
    {
        return timegm(&pTM);
    }
    else
    {
        lock_guard<mutex> lLock(sTimeMutex);
        time_t lRet;
        {
            CEnvChange lChange("TZ",pTZ.GetName());
            tzset();
            lRet=mktime(&pTM);
        }
        tzset();
        return lRet;
    }
}

static void time_t_to_tm(time_t pTime,tm &pTM,const CTimeZone &pTZ)
{
    if(pTZ.IsLocal())
    {
        lock_guard<mutex> lLock(sTimeMutex);
        localtime_r(&pTime,&pTM);
    }
    else if(pTZ.IsUTC())
    {
        gmtime_r(&pTime,&pTM);
    }
    else
    {
        lock_guard<mutex> lLock(sTimeMutex);
        {
            CEnvChange lChange("TZ",pTZ.GetName());
            tzset();
            localtime_r(&pTime,&pTM);
        }
        tzset();
    }
}

static inline void fill_tm(tm &pTM,const CDate &pDate)
{
    memset(&pTM,0,sizeof(pTM));
    pTM.tm_year=pDate.Year()-1900;
    pTM.tm_mon=pDate.Month()-1;
    pTM.tm_mday=pDate.Day();
    pTM.tm_isdst=-1;
}

static inline void fill_tm(tm &pTM,const CTimeOfDay &pTime)
{
    memset(&pTM,0,sizeof(pTM));
    pTM.tm_hour=pTime.Hour();
    pTM.tm_min=pTime.Min();
    pTM.tm_sec=pTime.Sec();
    pTM.tm_isdst=-1;
}

static inline void fill_tm(tm &pTM,const CDateTime &pDT)
{
    memset(&pTM,0,sizeof(pTM));
    pTM.tm_hour=pDT.Time().Hour();
    pTM.tm_min=pDT.Time().Min();
    pTM.tm_sec=pDT.Time().Sec();
    pTM.tm_year=pDT.Date().Year()-1900;
    pTM.tm_mon=pDT.Date().Month()-1;
    pTM.tm_mday=pDT.Date().Day();
    pTM.tm_isdst=-1;
}

static std::wstring format_date(const CDate &pDate,const wchar_t *pFormat)
{
    tm lTM;
    fill_tm(lTM,pDate);
    mktime(&lTM);
    wchar_t lOutStr[200];
    wcsftime(lOutStr,200,pFormat,&lTM);
    return std::wstring(lOutStr);
}

static std::wstring format_time(const CTimeOfDay &pTime,const wchar_t *pFormat)
{
    tm lTM;
    fill_tm(lTM,pTime);
    wchar_t lOutStr[200];
    wcsftime(lOutStr,200,pFormat,&lTM);
    return std::wstring(lOutStr);
}

static std::wstring format_datetime(const CDateTime &pDateTime,const wchar_t *pFormat)
{
    tm lTM;
    fill_tm(lTM,pDateTime);
    mktime(&lTM);
    lTM.tm_isdst=-1;
    wchar_t lOutStr[200];
    wcsftime(lOutStr,200,pFormat,&lTM);
    return std::wstring(lOutStr);
}

CDate::CDate(const CCalendarTime &pTime,const CTimeZone& pTZ)
{
    struct tm lTM;
    time_t_to_tm(CCalendarClock::to_time_t(pTime),lTM,pTZ);
    Set(lTM.tm_year+1900,lTM.tm_mon+1,lTM.tm_mday);
}

std::wstring CDate::GetText() const
{
    return format_date(*this,L"%x");
}

std::wstring CDate::GetWeekdayText() const
{
    return format_date(*this,L"%A");
}

std::wstring CDate::GetShortWeekdayText() const
{
    return format_date(*this,L"%a");
}

std::wstring CDate::GetMonthText() const
{
    return format_date(*this,L"%B");
}

std::wstring CDate::GetShortMonthText() const
{
    return format_date(*this,L"%b");
}

std::wstring CDate::GetMonthYearText() const
{
    return format_date(*this,L"%B %Y");
}

std::wstring CDate::GetISODate() const
{
    return format_date(*this,L"%Y%m%d");
}

CCalendarTime CDate::GetStartTime(const CTimeZone &pTZ) const
{
    struct tm lTM;
    fill_tm(lTM,*this);

    return CCalendarTime(chrono::seconds(tm_to_time_t(lTM,pTZ)));
}

CCalendarTime CDate::GetEndTime(const CTimeZone &pTZ) const
{
    return NextDay().GetStartTime(pTZ);
}

CTimeOfDay::CTimeOfDay(const CCalendarTime &pTime,const CTimeZone &pTZ)
{
    chrono::nanoseconds lSinceEpoch=pTime.time_since_epoch();
    chrono::seconds lWholeSeconds=chrono::duration_cast<chrono::seconds>(lSinceEpoch);
    struct tm lTM;
    time_t_to_tm((time_t)lWholeSeconds.count(),lTM,pTZ);
    Set(lTM.tm_hour,lTM.tm_min,lTM.tm_sec,(lSinceEpoch-lWholeSeconds).count());
}
std::wstring CTimeOfDay::GetText() const
{
    return format_time(*this,L"%X");
}

std::wstring CTimeOfDay::GetHourMinText() const
{
    return format_time(*this,L"%R");
}

CDateTime::CDateTime(const CCalendarTime &pTime,const CTimeZone &pTZ)
{
    chrono::nanoseconds lSinceEpoch=pTime.time_since_epoch();
    chrono::seconds lWholeSeconds=chrono::duration_cast<chrono::seconds>(lSinceEpoch);
    struct tm lTM;
    time_t_to_tm((time_t)lWholeSeconds.count(),lTM,pTZ);
    mTime.Set(lTM.tm_hour,lTM.tm_min,lTM.tm_sec,(lSinceEpoch-lWholeSeconds).count());
    mDate.Set(lTM.tm_year+1900,lTM.tm_mon+1,lTM.tm_mday);
    mTZ=pTZ;
}

std::wstring CDateTime::GetText() const
{
    return format_datetime(*this,L"%c");
}

std::wstring CDateTime::GetTextWithTimeZone() const
{
    if(mTZ.IsLocal())
    {
        lock_guard<mutex> lLock(sTimeMutex);
        return format_datetime(*this,L"%c %Z");
    }
    else
    {
        lock_guard<mutex> lLock(sTimeMutex);
        std::wstring lRet;
        {
            CEnvChange lChange("TZ",mTZ.GetName());
            tzset();
            lRet=format_datetime(*this,L"%c %Z");
        }
        tzset();
        return lRet;
    }
}

std::wstring CDateTime::GetISODateTime() const
{
    return format_datetime(*this,L"%Y%m%dT%H%M%SZ");
}

void CDateTime::Normalize()
{
    *this=CDateTime(GetCalendarTime());
}

CCalendarTime CDateTime::GetCalendarTime() const
{
    struct tm lTM;
    fill_tm(lTM,*this);
    return CCalendarTime(chrono::seconds(tm_to_time_t(lTM,mTZ))+chrono::microseconds(mTime.Nanosec()/1000));
}

/*namespace buola*/ }
